/*
* otfeed_symbol_list.cpp This example program illustrates the mechanism
* to be used for connecting to the otFeed servers and obtaining a list
* of symbols from the servers.
*/
#include "otfeed_servers.h"
#include <OTClient.h>
#include <boost/bind.hpp>
#include <boost/program_options.hpp>
#include <sstream>
#include <iostream>
#include <iomanip>

namespace
{
    /**
     * Client class used to login and obtain the list of symbols from 
     * otFeed servers. 
     */
    class SymbolListClient : public OTClient
    {
    public:
        /**
         * Default constructor.
         */
        SymbolListClient( const std::string exchangeName ) ;

        /**
         * Return true if a list of exchanges has been received by the client.
         */
        bool
        IsExchangeListReceived() const ;

        /**
         * Return true if the entire symbol list for the specified exchange 
         * has been received from the opentick server(s).
         */
        bool
        IsSymbolListReceived() const ;

        /**
         * Return true if an error was encountered while communicating with
         * the otFeed servers.
         */
        bool
        IsErrorEncountered() const ;

        //
        // Overloaded OTClient methids.
        //
        virtual void onLogin() ;

        virtual void onListExchanges( OTExchange* exchangeList,
                                      int numOfExchanges ) ;

        virtual void onListSymbols( OTSymbol* symbolList,
                                    int numOfSymbols ) ;

        virtual void onError( OTError* otError ) ;

        virtual void onMessage( OTMessage* otMessage ) ;
    private:
        //! Flag that indicates if the exchange list has been received.
        bool mIsExchangeListReceived ;

        //! Flag that indicates if the entire symbol list has been received.
        bool mIsSymbolListReceived ;

        /**
         * Identifier for the request for list of symbols. 
         */
        int mListSymbolsRequestId ;

        /**
         * Name of the exchange for which a list of symbols is to be obtained. 
         */
        std::string mExchangeName ;

        /**
         * Flag that indicates if an error has been encoutered while communicating
         * with the otFeed server.
         */
        bool mIsErrorEncountered ;
    } ;
}

int main( int argc,
          char** argv )
{
    /*
     * Use boost::program_options to parse the command line for options.
     */
    namespace po = boost::program_options ;
    //
    // Specify the list of allowed command line options.
    //
    po::options_description allowed_options( "Allowed options" ) ;
    allowed_options.add_options()
    ( "help,h", "Display help message." )
    ( "user,u", po::value<std::string>(), "OpenTick account/user name" )
    ( "password,p", po::value<std::string>(), "OpenTick account/user password" )
    ( "exchange,e", po::value<std::string>(), "Exchange code for which symbol list is to be obtained" )
    ;
    //
    // Parse the command line and extract options from it.
    //
    po::variables_map user_specified_options ;
    po::store( po::parse_command_line( argc, argv, allowed_options ),
               user_specified_options ) ;
    po::notify( user_specified_options ) ;
    //
    // If the "help" option is specified then display the help message and
    // exit immediately.
    //
    if ( user_specified_options.count( "help" ) )
    {
        std::cout << allowed_options << std::endl ;
        return EXIT_SUCCESS ;
    }
    //
    // The user must specify an exchange name. If no exchange name is specified
    // then display an error message. 
    //
    std::string exchange_code ;
    if ( user_specified_options.count( "exchange" ) )
    {
    	exchange_code = user_specified_options[ "exchange" ].as<std::string>() ;
    }
    else
    {
    	std::cerr << "ERROR: No exchange code specified." << std::endl ;
    	std::cerr << std::endl ;
    	std::cerr << allowed_options << std::endl ;
    	return EXIT_FAILURE ;
    }
    //
    // If the username is not specified then ask the user to type in the
    // user name otherwise store the specified user name.
    //
    std::string login ;
    if ( user_specified_options.count( "user" ) )
    {
        login = user_specified_options[ "user" ].as<std::string>() ;
    }
    else
    {
        std::cout << "Please, enter your login: " ;
        std::cin >> login ;
    }
    //
    // Similarly, if the password is specified then save it otherwise 
    // ask the user for the password. 
    //
    std::string password ;
    if ( user_specified_options.count( "password" ) )
    {
    	password = user_specified_options[ "password" ].as<std::string>() ;
    }
    else
    {
    	std::ostringstream password_prompt ;
    	password_prompt << "Password for " << login << ": " ;
    	//
    	password = getpass( password_prompt.str().c_str() ) ;
    }
    //
    // Create an instance of our sample client and try to log in to
    // the server.
    //
    SymbolListClient client( exchange_code ) ;
    for ( int i = 0; i < OtfeedUtilities::NUM_OF_OTFEED_SERVERS; ++i )
    {
        //
        // :KLUDGE: The const_cast<> is necessary below because OTClient::addHost
        // requires a 'char*' argument even though it should really be a
        // 'const char*' or a 'const std::string'. Hopefully this will change
        // in future version of otFeed API and we will not need the const_cast<>
        // at that point.
        //
        client.addHost( const_cast<char*>( OtfeedUtilities::OTFEED_SERVERS[ i ].mAddress.c_str() ),
                        OtfeedUtilities::OTFEED_SERVERS[ i ].mPort ) ;
    }
    //
    std::cout << "Logging in to the otFeed servers..." << std::endl ;
    //
    // :KLUDGE: OTClient::login() takes two 'char*' arguments in the
    // current version of OTFeed. However, it should really take
    // 'const char*' arguments. In order to use the current version of
    // OTClient::login() we have to use a const_cast<> below. Hope
    // that this will be fixed in future versions.
    //
    client.login( const_cast<char*>( login.c_str() ),
                  const_cast<char*>( password.c_str() ) ) ;
    //
    // Wait for the client to request and receive the symbol list.
    //
    while ( ( ! client.IsSymbolListReceived() ) &&
            ( ! client.IsErrorEncountered() ) )
    {
        sleep( 1 ) ;
    }
    //
    if ( client.IsErrorEncountered() )
    {
        return EXIT_FAILURE ;
    }
    return EXIT_SUCCESS ;
}

namespace
{
    inline
    SymbolListClient::SymbolListClient( const std::string exchangeName ) :
            mIsExchangeListReceived( false ),
            mExchangeName( exchangeName ),
            mIsErrorEncountered( false ),
            mIsSymbolListReceived( false ),
            mListSymbolsRequestId( -1 )
    {
        /* empty */
    }

    inline
    bool
    SymbolListClient::IsExchangeListReceived() const
    {
        return mIsExchangeListReceived ;
    }

    inline
    bool
    SymbolListClient::IsErrorEncountered() const
    {
        return mIsErrorEncountered ;
    }

    inline
    bool
    SymbolListClient::IsSymbolListReceived() const
    {
        return mIsSymbolListReceived ;
    }

    void
    SymbolListClient::onLogin()
    {
        std::cout << "Login successful."
        << std::endl ;
        std::cout << "Requesting list of exchanges ... " ;
        //
        // Request a list of exchanges that can be accessed by the current
        // user. The onListExchanges() method will be called when this is received
        // from the server.
        //
        const int request_id = this->requestListExchanges() ;
        std::cout << "done." << std::endl ;
        //
        return ;
    }

    void
    SymbolListClient::onListExchanges( OTExchange* const exchangeList,
                                       const int numOfExchanges )
    {
        std::cout << "List of exchanges received."
        << std::endl ;
        /**
         * Check if the specified exchange is one of the exchanges supported
         * by the opentick servers.
         */ 
        //
        // If the exchage list is empty then there was an error while
        // communicating with the opentick servers.
        //
        if ( ( 0 == exchangeList ) ||
             ( 0 == numOfExchanges ) )
        {
            std::cerr << __FILE__ << ":" << __LINE__ << ": "
            << "ERROR: Empty exchange list received from servers."
            << std::endl ;
            //
            mIsErrorEncountered = true ;
            return ;
        }
        //
        // Check if mExchangeName is in exchangeList.
        //
        if ( 0 == std::count_if( exchangeList,
                                 exchangeList + numOfExchanges,
                                 ( boost::bind( &OTExchange::getCode, _1 ) == mExchangeName )
                               ) )
        {
            cerr << __FILE__ << ":" << __LINE__ << ": "
            << "Exchange \"" << mExchangeName << "\" not supported."
            << std::endl ;
            mIsErrorEncountered = true ;
            return ;
        }
        //
        // Now that we have received the exchange list and verified that the
        // specified exchange, mExchangeName, is supported by the servers,
        // ask the servers to send us a list of symbols for the exchange.
        //
        this->mIsExchangeListReceived = true ;
        std::clog << "Exchange \"" << mExchangeName << "\" is valid." << std::endl ;
        std::clog << "Requesting list of symbols for the exchange." << std::endl ;
        mListSymbolsRequestId = this->requestListSymbols( mExchangeName.c_str() ) ;
        return ;
    }

    void
    SymbolListClient::onListSymbols( OTSymbol* symbolList,
                                     int numOfSymbols )
    {
        for ( int i = 0; i < numOfSymbols; ++i )
        {
            std::clog << symbolList[ i ].getCode() << "\t" << symbolList[ i ].getCompany() << std::endl ;
        }
        return ;
    }

    void
    SymbolListClient::onError( OTError* const otError )
    {
        //
        // Display a message indicating the error type and description.
        //
        std::ostringstream error_message ;
        error_message << __FILE__ << ":" << __LINE__ << ": "
        << "ERROR: [" << otError->getRequestId()
        << ":" << otError->getCode()
        << "] "
        << otError->getDescription()
        << "."
        << std::endl ;
        std::cerr << error_message.str() ;
        //
        // Set a flag indicating that processing has stopped due to an
        // error. Unfortunately, one cannot throw an exception here because
        // of the way otfeed library is implemented; it catches all exceptions
        // through by Client::onError() and simply ignores them. This will
        // hopefully be fixed in future.
        //
        this->mIsErrorEncountered = true ;
        return ;
    }

    void
    SymbolListClient::onMessage( OTMessage* otMessage )
    {
        //
        // :TRICKY: When a list of symbols is requested from the server(s),
        // the onListSymbols() method is called multiple times with a partial
        // list. When the entire list has been transmitted by the server, the
        // onMessage() method is called with message code of OT_LIST_SYMBOLS.
        // We use this message to indicate that the entire list has been
        // received.
        //
        if ( ( 0 != otMessage ) &&
             ( otMessage->getRequestId() == mListSymbolsRequestId ) &&
             ( OT_LIST_SYMBOLS == otMessage->getCode() ) )
        {
            std::clog << std::endl
            << "Message received: "
            << "[" << otMessage->getCode() << "] "
            << otMessage->getDescription()
            << std::endl ;
            //
            // The entire list of symbols has been received.
            //
            this->mIsSymbolListReceived = true ;
        }
        return ;
    }

}
